feat(i2c): Improve I2C reliability, DMA throughput, and board support#42
feat(i2c): Improve I2C reliability, DMA throughput, and board support#42gqsdjhh wants to merge 7 commits intoAlliance-Algorithm:mainfrom
Conversation
Walkthrough该拉取请求为 librmcs 项目添加了完整的 I2C(Inter-Integrated Circuit)总线支持,涵盖协议定义、固件驱动、中断处理、应用集成和主机 API 多个层级,支持读写操作、可选寄存器寻址、DMA 传输和错误恢复。 Changes
Sequence Diagram(s)sequenceDiagram
participant Host as Host Agent
participant Serializer as Serializer<br/>(Protocol)
participant Deserializer as Deserializer<br/>(Protocol)
participant FW as Firmware I2C<br/>Driver
participant HAL as STM32 HAL<br/>& DMA
participant Device as I2C<br/>Device
Host->>Serializer: write_i2c_write(kI2c0, I2cDataView)
Serializer->>Serializer: Validate slave_address,<br/>encode I2cHeader
Serializer-->>Host: SerializeResult::kSuccess
Host->>Deserializer: Feed serialized bytes
Deserializer->>Deserializer: process_i2c_field()<br/>peek/validate header
Deserializer->>FW: i2c_write_deserialized_callback<br/>(kI2c0, I2cDataView)
FW->>FW: Enqueue write request<br/>into ring buffer
FW->>HAL: Start DMA write transfer<br/>via HAL_I2C_Master_Transmit_DMA
HAL->>Device: Send slave address<br/>+ payload bytes
Device-->>HAL: ACK
HAL->>FW: tx_complete_callback()
FW->>FW: Dequeue request,<br/>release payload chunks
FW->>FW: Process next queued<br/>request or idle
sequenceDiagram
participant Host as Host Agent
participant Serializer as Serializer<br/>(Protocol)
participant Deserializer as Deserializer<br/>(Protocol)
participant FW as Firmware I2C<br/>Driver
participant HAL as STM32 HAL<br/>& DMA
participant Device as I2C<br/>Device
Host->>Serializer: write_i2c_read_config<br/>(kI2c0, I2cReadConfigView)
Serializer->>Serializer: Validate read_length,<br/>encode I2cHeader
Serializer-->>Host: SerializeResult::kSuccess
Host->>Deserializer: Feed serialized bytes
Deserializer->>Deserializer: process_i2c_field()<br/>peek header, branch on<br/>kReadRequest
Deserializer->>FW: i2c_read_config_<br/>deserialized_callback
FW->>FW: Enqueue read request<br/>into ring buffer
FW->>HAL: Start DMA read transfer<br/>via HAL_I2C_Master_Receive_DMA
HAL->>Device: Request slave address<br/>+ read N bytes
Device-->>HAL: Send data bytes
HAL->>FW: rx_complete_callback()
FW->>FW: Copy DMA buffer<br/>to read_result
FW->>Serializer: write_i2c_read_result<br/>(kI2c0, payload)
Serializer-->>Host: kSuccess
Host->>Host: Receive I2C data<br/>via i2c_receive_callback
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
host/include/librmcs/agent/rmcs_board.hpp (1)
151-163: 这里的重写函数请显式加上override。
final只能限制继续覆写,不能替代override。这里最好和仓库其它 host 头文件保持同一套约定。建议修改
- bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) final { + bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) override final { switch (id) { case data::DataId::kI2c0: i2c0_receive_callback(data); return true; default: return false; } } - void i2c_error_callback(data::DataId id, uint8_t slave_address) final { + void i2c_error_callback(data::DataId id, uint8_t slave_address) override final { switch (id) { case data::DataId::kI2c0: i2c0_error_callback(slave_address); break; default: break; } }Based on learnings: In the librmcs codebase, when overriding virtual functions from a base class in C++, declare overrides with the override specifier and do not repeat virtual on the overriding function.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@host/include/librmcs/agent/rmcs_board.hpp` around lines 151 - 163, Add the override specifier to the two overridden member functions so they follow the project's convention (use both override and final). Specifically update the declarations for i2c_receive_callback(data::DataId id, const data::I2cDataView& data) and i2c_error_callback(data::DataId id, uint8_t slave_address) to include override (in addition to final) while keeping their current logic that dispatches to i2c0_receive_callback and i2c0_error_callback for data::DataId::kI2c0.host/include/librmcs/agent/c_board.hpp (1)
144-156: 这里的重写函数请显式加上override。
final不能替代override;当前写法和仓库里约定的重写风格不一致,也更容易漏掉签名漂移。建议修改
- bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) final { + bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) override final { switch (id) { case data::DataId::kI2c0: i2c0_receive_callback(data); return true; default: return false; } } - void i2c_error_callback(data::DataId id, uint8_t slave_address) final { + void i2c_error_callback(data::DataId id, uint8_t slave_address) override final { switch (id) { case data::DataId::kI2c0: i2c0_error_callback(slave_address); break; default: break; } }Based on learnings: In the librmcs codebase, when overriding virtual functions from a base class in C++, declare overrides with the override specifier and do not repeat virtual on the overriding function.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@host/include/librmcs/agent/c_board.hpp` around lines 144 - 156, Add the explicit override specifier to the two overridden methods so they follow the repo's override style: update i2c_receive_callback(data::DataId, const data::I2cDataView&) and i2c_error_callback(data::DataId, uint8_t) to include override (keeping final if desired) instead of relying on final alone; do not add the virtual keyword.core/src/protocol/serializer.hpp (1)
563-586: 建议在required_i2c_size内补充 payload 语义级约束,避免未来误用生成非法帧。当前仅校验了长度上限和 payload 枚举值。建议进一步约束:
kError必须data_len == 0 && has_register == falsekReadRequest必须payload_bytes == 0(已隐式满足),并可显式保留这样可把协议合法性收敛到单点校验,减少后续调用方误用风险。
♻️ 建议修改
static std::size_t required_i2c_size( FieldId field_id, I2cHeader::PayloadEnum payload, std::size_t data_len, bool has_register) noexcept { LIBRMCS_VERIFY_LIKELY(is_i2c_field_id(field_id), 0); LIBRMCS_VERIFY_LIKELY(data_len <= ((1U << 9) - 1U), 0); switch (payload) { case I2cHeader::PayloadEnum::kWrite: case I2cHeader::PayloadEnum::kReadRequest: case I2cHeader::PayloadEnum::kReadResult: case I2cHeader::PayloadEnum::kError: break; default: return 0; } + if (payload == I2cHeader::PayloadEnum::kError) { + LIBRMCS_VERIFY_LIKELY(!has_register && data_len == 0, 0); + } const std::size_t field_header_bytes = required_field_header_size(field_id); const std::size_t i2c_header_bytes = sizeof(I2cHeader); const std::size_t register_bytes = has_register ? 1 : 0;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/src/protocol/serializer.hpp` around lines 563 - 586, In required_i2c_size, add semantic checks for payload-specific constraints: verify that when payload == I2cHeader::PayloadEnum::kError then data_len == 0 and has_register == false, and explicitly assert that payload == I2cHeader::PayloadEnum::kReadRequest implies payload_bytes == 0 (or equivalently data_len == 0) to make the contract explicit; implement these as LIBRMCS_VERIFY_LIKELY checks (matching existing style) near the existing payload switch so invalid combinations return 0, and reference the function required_i2c_size and the enum values I2cHeader::PayloadEnum::kError and kReadRequest when adding the checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core/src/protocol/deserializer.cpp`:
- Around line 363-369: The kError branch in I2cHeader::PayloadEnum::kError
currently returns without consuming optional register or payload bytes, causing
stream desynchronization; update the branch in deserializer.cpp to inspect the
header's has_register and data_length fields and consume/skip the corresponding
bytes from the input buffer/stream (e.g., read and discard the register byte if
has_register is true and advance by data_length bytes for payload) before
constructing data::I2cDataView (with empty payload) and invoking
callback_.i2c_error_deserialized_callback(field_id, data_view); if you already
have a helper like consumeBytes/advanceCursor or existing read routines, reuse
them to avoid duplicating parsing logic.
- Line 307: I2C 分支没有对 data_length(从 header.get<I2cHeader::DataLength>()
读取)做上界校验就传给 peek_bytes,可能导致超大长度把错误处理推迟并引发边界风险;请在使用 peek_bytes 或后续缓冲访问前参考 UART
分支的做法,对 data_length 做上界限制(例如 clamp 到内部缓冲容量或最大允许帧长),并在长度异常时直接返回/报错;修改点包含使用
header.get<I2cHeader::DataLength>() 的位置、传入 peek_bytes
的调用处以及与之对应的错误路径(同样修复类似位置的另两处出现处)。
In `@firmware/c_board/app/src/usb/vendor.hpp`:
- Around line 148-160: 这两个回调 i2c_read_result_deserialized_callback 和
i2c_error_deserialized_callback 不应在接收到协议级错误时调用
core::utility::assert_failed_always()
导致硬故障;请移除或替换断言,在处理不可恢复数据时改为记录/丢弃输入并安全返回(例如调用适当的日志函数或统计错误计数器,并直接
return),确保不会中断整个固件流程;保留函数签名和任何必需的 no-op 行为以满足接口契约。
In `@firmware/rmcs_board/app/src/i2c/i2c.hpp`:
- Around line 33-35: 当前实现对 data.payload.empty() 直接 return
导致空写入被静默忽略,应改为上报非法长度错误并让调用方感知失败;在处理写请求的函数(检查 data.payload.empty() 的位置)移除裸
return,改为通过项目现有的 I2C 错误上报/返回机制上报一个“非法长度”错误(例如调用错误上报函数或返回错误码/false、或设置 I2C
错误状态),确保 payload.empty() 情况不会被当作成功处理。
- Around line 41-51: The code currently const_casts data.payload into a mutable
pointer and passes it to HPM APIs (i2c_master_write and
i2c_master_address_write), which can mutate the buffer; instead allocate a local
mutable buffer, copy data.payload into it, and pass that mutable buffer to
i2c_master_write/i2c_master_address_write (use payload/local_buffer and ensure
size uses data.payload.size()); update both branches (the reg_address branch
using i2c_master_address_write and the plain write branch using
i2c_master_write) to use the temporary writable buffer and free or let it go out
of scope after the call.
In `@firmware/rmcs_board/app/src/usb/vendor.hpp`:
- Around line 158-170: Replace the unconditional hard assert in
i2c_read_result_deserialized_callback and i2c_error_deserialized_callback with
non-fatal handling: detect that the packet direction is incorrect, drop the
message, emit a soft error/reporting call (e.g., increment an error counter,
call a logger or core::diagnostics::report_error) and optionally use a
debug-only assert or log for development builds; ensure both functions stop
calling core::utility::assert_failed_always() and instead record the error and
return gracefully so the device remains usable.
---
Nitpick comments:
In `@core/src/protocol/serializer.hpp`:
- Around line 563-586: In required_i2c_size, add semantic checks for
payload-specific constraints: verify that when payload ==
I2cHeader::PayloadEnum::kError then data_len == 0 and has_register == false, and
explicitly assert that payload == I2cHeader::PayloadEnum::kReadRequest implies
payload_bytes == 0 (or equivalently data_len == 0) to make the contract
explicit; implement these as LIBRMCS_VERIFY_LIKELY checks (matching existing
style) near the existing payload switch so invalid combinations return 0, and
reference the function required_i2c_size and the enum values
I2cHeader::PayloadEnum::kError and kReadRequest when adding the checks.
In `@host/include/librmcs/agent/c_board.hpp`:
- Around line 144-156: Add the explicit override specifier to the two overridden
methods so they follow the repo's override style: update
i2c_receive_callback(data::DataId, const data::I2cDataView&) and
i2c_error_callback(data::DataId, uint8_t) to include override (keeping final if
desired) instead of relying on final alone; do not add the virtual keyword.
In `@host/include/librmcs/agent/rmcs_board.hpp`:
- Around line 151-163: Add the override specifier to the two overridden member
functions so they follow the project's convention (use both override and final).
Specifically update the declarations for i2c_receive_callback(data::DataId id,
const data::I2cDataView& data) and i2c_error_callback(data::DataId id, uint8_t
slave_address) to include override (in addition to final) while keeping their
current logic that dispatches to i2c0_receive_callback and i2c0_error_callback
for data::DataId::kI2c0.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 278cff01-558b-4ce2-a2ae-7ecc08f0e769
📒 Files selected for processing (27)
.codexcore/include/librmcs/data/datas.hppcore/src/protocol/deserializer.cppcore/src/protocol/deserializer.hppcore/src/protocol/protocol.hppcore/src/protocol/serializer.hppfirmware/c_board/app/src/app.cppfirmware/c_board/app/src/i2c/i2c.cppfirmware/c_board/app/src/i2c/i2c.hppfirmware/c_board/app/src/usb/vendor.hppfirmware/c_board/bsp/cubemx/Core/Inc/i2c.hfirmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_hal_conf.hfirmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_it.hfirmware/c_board/bsp/cubemx/Core/Src/dma.cfirmware/c_board/bsp/cubemx/Core/Src/gpio.cfirmware/c_board/bsp/cubemx/Core/Src/i2c.cfirmware/c_board/bsp/cubemx/Core/Src/main.cfirmware/c_board/bsp/cubemx/Core/Src/stm32f4xx_it.cfirmware/c_board/bsp/cubemx/cmake/stm32cubemx/CMakeLists.txtfirmware/c_board/bsp/cubemx/rmcs_slave.iocfirmware/rmcs_board/app/src/app.cppfirmware/rmcs_board/app/src/i2c/i2c.hppfirmware/rmcs_board/app/src/usb/vendor.hpphost/include/librmcs/agent/c_board.hpphost/include/librmcs/agent/rmcs_board.hpphost/include/librmcs/protocol/handler.hpphost/src/protocol/handler.cpp
| payload_type = header.get<I2cHeader::PayloadType>(); | ||
| has_register = header.get<I2cHeader::HasRegister>(); | ||
| slave_address = header.get<I2cHeader::SlaveAddress>(); | ||
| data_length = header.get<I2cHeader::DataLength>(); |
There was a problem hiding this comment.
缺少 I2C data_length 上界校验。
data_length 直接用于 peek_bytes,但未像 UART 分支那样限制到内部缓冲能力;超大长度输入会把错误处理路径推迟到更深处,增加边界风险。
🔧 建议修改
data_length = header.get<I2cHeader::DataLength>();
consume_peeked();
}
if (slave_address > 0x7F) [[unlikely]]
co_return false;
+
+if (data_length > sizeof(pending_bytes_buffer_)) [[unlikely]]
+ co_return false;Also applies to: 329-334, 349-360
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core/src/protocol/deserializer.cpp` at line 307, I2C 分支没有对 data_length(从
header.get<I2cHeader::DataLength>() 读取)做上界校验就传给
peek_bytes,可能导致超大长度把错误处理推迟并引发边界风险;请在使用 peek_bytes 或后续缓冲访问前参考 UART 分支的做法,对
data_length 做上界限制(例如 clamp 到内部缓冲容量或最大允许帧长),并在长度异常时直接返回/报错;修改点包含使用
header.get<I2cHeader::DataLength>() 的位置、传入 peek_bytes
的调用处以及与之对应的错误路径(同样修复类似位置的另两处出现处)。
| case I2cHeader::PayloadEnum::kError: { | ||
| data::I2cDataView data_view{}; | ||
| data_view.slave_address = slave_address; | ||
| data_view.payload = std::span<const std::byte>{}; | ||
| data_view.has_register = false; | ||
| callback_.i2c_error_deserialized_callback(field_id, data_view); | ||
| break; |
There was a problem hiding this comment.
kError 分支未消费额外字节会导致流错位。
当报文头里 has_register 或 data_length 非零时,当前分支直接回调并返回成功,剩余字节会被当成下一帧头解析,导致后续解析链路错位。
🔧 建议修改
case I2cHeader::PayloadEnum::kError: {
+ if (has_register || data_length != 0) [[unlikely]]
+ co_return false;
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.payload = std::span<const std::byte>{};
data_view.has_register = false;
callback_.i2c_error_deserialized_callback(field_id, data_view);
break;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case I2cHeader::PayloadEnum::kError: { | |
| data::I2cDataView data_view{}; | |
| data_view.slave_address = slave_address; | |
| data_view.payload = std::span<const std::byte>{}; | |
| data_view.has_register = false; | |
| callback_.i2c_error_deserialized_callback(field_id, data_view); | |
| break; | |
| case I2cHeader::PayloadEnum::kError: { | |
| if (has_register || data_length != 0) [[unlikely]] | |
| co_return false; | |
| data::I2cDataView data_view{}; | |
| data_view.slave_address = slave_address; | |
| data_view.payload = std::span<const std::byte>{}; | |
| data_view.has_register = false; | |
| callback_.i2c_error_deserialized_callback(field_id, data_view); | |
| break; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core/src/protocol/deserializer.cpp` around lines 363 - 369, The kError branch
in I2cHeader::PayloadEnum::kError currently returns without consuming optional
register or payload bytes, causing stream desynchronization; update the branch
in deserializer.cpp to inspect the header's has_register and data_length fields
and consume/skip the corresponding bytes from the input buffer/stream (e.g.,
read and discard the register byte if has_register is true and advance by
data_length bytes for payload) before constructing data::I2cDataView (with empty
payload) and invoking callback_.i2c_error_deserialized_callback(field_id,
data_view); if you already have a helper like consumeBytes/advanceCursor or
existing read routines, reuse them to avoid duplicating parsing logic.
| uint8_t slave_address; | ||
| { | ||
| const utility::InterruptLockGuard guard; | ||
|
|
||
| if (transfer_state_.load(std::memory_order::relaxed) == TransferState::kIdle) | ||
| return; | ||
|
|
||
| if (!timer::timer->check_expired(transfer_start_timepoint_, kTransferTimeout)) | ||
| return; | ||
|
|
||
| slave_address = active_request_.slave_address; | ||
| abort_dma_if_enabled(hal_i2c_handle_->hdmarx); | ||
| abort_dma_if_enabled(hal_i2c_handle_->hdmatx); | ||
| core::utility::assert_always(HAL_I2C_DeInit(hal_i2c_handle_) == HAL_OK); | ||
| core::utility::assert_always(HAL_I2C_Init(hal_i2c_handle_) == HAL_OK); | ||
| finish_active_request_locked(); |
There was a problem hiding this comment.
❓ Verification inconclusive
🏁 Script executed:
#!/bin/bash
set -e
echo '--- interrupt_lock.hpp ---'
fd -i 'interrupt_lock.hpp$' | xargs -r sed -n '1,200p'
echo
echo '--- HAL_DMA_Abort ---'
fd -i 'stm32f4xx_hal_dma.c$' | xargs -r rg -n -A30 -B5 '\bHAL_DMA_Abort\s*\('
echo
echo '--- HAL_I2C_Init / HAL_I2C_DeInit ---'
fd -i 'stm32f4xx_hal_i2c.c$' | xargs -r rg -n -A40 -B5 '\bHAL_I2C_(Init|DeInit)\s*\('Repository: Alliance-Algorithm/librmcs
Length of output: 1818
超时恢复路径中 HAL 调用与全局关中断的交互风险
Lines 299-312(及 319-326)中,InterruptLockGuard 作用域内调用 HAL_DMA_Abort()、HAL_I2C_DeInit()、HAL_I2C_Init() 存在潜在风险。若该 guard 屏蔽全局中断(包括 SysTick),而这些 HAL 函数依赖轮询或超时机制,可能导致该恢复路径在超时条件下卡死。
建议验证:
InterruptLockGuard的具体实现及其屏蔽范围- 相关 HAL 函数的超时轮询依赖关系
如确认存在此风险,应改为"临界区内仅摘取状态,HAL 调用移至临界区外"。
| void i2c_read_result_deserialized_callback( | ||
| core::protocol::FieldId id, const data::I2cDataView& data) override { | ||
| (void)id; | ||
| (void)data; | ||
| core::utility::assert_failed_always(); | ||
| } | ||
|
|
||
| void i2c_error_deserialized_callback( | ||
| core::protocol::FieldId id, const data::I2cDataView& data) override { | ||
| (void)id; | ||
| (void)data; | ||
| core::utility::assert_failed_always(); | ||
| } |
There was a problem hiding this comment.
避免把可恢复协议异常升级为硬故障。
i2c_read_result 与 i2c_error 当前无条件 assert_failed_always(),异常输入会直接导致固件停机,建议改为丢弃/记录错误。
🔧 建议修改
void i2c_read_result_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
- core::utility::assert_failed_always();
+ return;
}
void i2c_error_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
- core::utility::assert_failed_always();
+ return;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@firmware/c_board/app/src/usb/vendor.hpp` around lines 148 - 160, 这两个回调
i2c_read_result_deserialized_callback 和 i2c_error_deserialized_callback
不应在接收到协议级错误时调用 core::utility::assert_failed_always()
导致硬故障;请移除或替换断言,在处理不可恢复数据时改为记录/丢弃输入并安全返回(例如调用适当的日志函数或统计错误计数器,并直接
return),确保不会中断整个固件流程;保留函数签名和任何必需的 no-op 行为以满足接口契约。
| if (data.payload.empty()) | ||
| return; | ||
|
|
There was a problem hiding this comment.
空写入长度被静默忽略,应按非法长度上报 I2C 错误。
这里直接返回会让上层误判为“已处理”。与本 PR“非法长度上报错误”的目标不一致。
🐛 建议修改
void handle_downlink_write(const data::I2cDataView& data) {
- if (data.payload.empty())
+ if (data.payload.empty()) {
+ write_error(data.slave_address);
return;
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@firmware/rmcs_board/app/src/i2c/i2c.hpp` around lines 33 - 35, 当前实现对
data.payload.empty() 直接 return 导致空写入被静默忽略,应改为上报非法长度错误并让调用方感知失败;在处理写请求的函数(检查
data.payload.empty() 的位置)移除裸 return,改为通过项目现有的 I2C
错误上报/返回机制上报一个“非法长度”错误(例如调用错误上报函数或返回错误码/false、或设置 I2C 错误状态),确保 payload.empty()
情况不会被当作成功处理。
| const auto* payload = reinterpret_cast<const uint8_t*>(data.payload.data()); | ||
| hpm_stat_t status = status_fail; | ||
| if (data.has_register) { | ||
| uint8_t reg_address = data.reg_address; | ||
| status = i2c_master_address_write( | ||
| i2c_base_, data.slave_address, ®_address, sizeof(reg_address), | ||
| const_cast<uint8_t*>(payload), static_cast<uint32_t>(data.payload.size())); | ||
| } else { | ||
| status = i2c_master_write( | ||
| i2c_base_, data.slave_address, const_cast<uint8_t*>(payload), | ||
| static_cast<uint32_t>(data.payload.size())); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate hpm_i2c_drv.h =="
fd -i 'hpm_i2c_drv.h'
HEADER="$(fd -i 'hpm_i2c_drv.h' | head -n 1)"
echo "== inspect API signatures in ${HEADER} =="
rg -n -C3 'i2c_master_(address_)?write\s*\(' "$HEADER"
rg -n -C3 'i2c_master_(address_)?read\s*\(' "$HEADER"
echo "== inspect driver implementation for tx-buffer writes =="
fd -i 'hpm_i2c*.c' | xargs -r rg -n -C2 'i2c_master_(address_)?write\s*\(|\*\s*[a-zA-Z_][a-zA-Z0-9_]*\s*=|\[[^]]+\]\s*='Repository: Alliance-Algorithm/librmcs
Length of output: 1818
🌐 Web query:
HPM I2C API i2c_master_write i2c_master_address_write const buffer semantics
💡 Result:
In the HPMicro (HPM) SDK I2C driver API (hpm_i2c_drv.h), the functions i2c_master_write and i2c_master_address_write are blocking polling-mode APIs for I2C master transmit operations. i2c_master_write: Performs a standard I2C write to a slave device (device_address), sending 'size' bytes from 'buf'. Prototype: hpm_stat_t i2c_master_write(I2C_Type *ptr, const uint16_t device_address, uint8_t *buf, const uint32_t size). The 'buf' parameter is non-const (uint8_t *), indicating the driver may modify buffer contents (e.g., for status or NACK handling), despite being transmit-only. i2c_master_address_write: Performs a register write: sends device_address, then 'addr_size_in_byte' bytes from 'addr', then 'size_in_byte' bytes from 'buf'. Prototype: hpm_stat_t i2c_master_address_write(I2C_Type *ptr, const uint16_t device_address, uint8_t *addr, uint32_t addr_size_in_byte, uint8_t *buf, const uint32_t size_in_byte). Both 'addr' and 'buf' are non-const (uint8_t ), same semantics as above—the driver does not promise not to modify them. Note: Higher-level component APIs (hpm_i2c_) use uint8_t *buf (non-const). No evidence of const qualifiers in low-level driver prototypes; buffers should not be const-qualified data to avoid undefined behavior if modified.
Citations:
- 1: https://hpm-sdk.readthedocs.io/zh-cn/latest/api_doc/group__i2c__interface.html
- 2: https://hpm-sdk.readthedocs.io/en/v1.8.0/_static/api_doc/html/hpm__i2c_8h.html
- 3: https://hpm-sdk.readthedocs.io/en/v1.10.0/components/i2c/README_en.html
- 4: https://hpm-sdk.readthedocs.io/en/latest/api_doc/hpm__i2c_8h.html
- 5: https://hpm-sdk.readthedocs.io/en/latest/api%5Fdoc/globals_func_i.html
- 6: https://hpm-sdk.readthedocs.io/zh-cn/v1.10.0/samples/drivers/i2c/index_zh.html
🌐 Web query:
HPM SDK RISC-V I2C driver API reference hpm_i2c_drv.h
💡 Result:
The hpm_i2c_drv.h file is the header for the I2C driver API in HPMicro's HPM SDK, used with their RISC-V based MCUs. It provides low-level driver functions for I2C peripherals. Key features from documentation: - Supports standard (100 kbps), fast (400 kbps), and fast-plus (1 Mbps) modes. - Supports 7-bit and 10-bit addressing. - Master and slave modes. - Interrupt and DMA support (tx/rx complete, byte received/sent, start/stop, arbitration lost, address hit, FIFO events). - 4-byte hardware FIFO (size defined in hpm_soc_feature.h as I2C_SOC_FIFO_SIZE). - Bus deadlock recovery (if HPM_IP_FEATURE_I2C_SUPPORT_RESET defined). Main APIs (from driver samples and component docs): - hpm_stat_t i2c_init_master(I2C_Type *ptr, uint32_t src_clk_in_hz, i2c_config_t *config); - hpm_stat_t i2c_master_write(I2C_Type *ptr, uint16_t device_address, uint8_t *buf, uint32_t size); (polling) - hpm_stat_t i2c_master_read(I2C_Type *ptr, uint16_t device_address, uint8_t *buf, uint32_t size); (polling) - hpm_stat_t i2c_enable_irq(I2C_Type *ptr, uint32_t mask); - uint32_t i2c_get_status(I2C_Type *ptr); for IRQ handling. - DMA APIs available via i2c_components layer. Source code and full API reference: - View source: https://hpm-sdk.readthedocs.io/en/latest/api_doc/hpmi2cdrv_8h_source.html - Directory listing confirms presence: https://hpm-sdk.readthedocs.io/en/latest/api_doc/dir_445f1c1c73deccb146d16af27ad5c1d1.html (under drivers/inc/) - SDK repo: https://github.com/hpmicro/hpm_sdk (latest v1.11.0 as of 2025-12-31) - Higher-level component: https://hpm-sdk.readthedocs.io/en/v1.10.0/components/i2c/README_en.html (hpm_i2c_context_t, blocking/non-blocking tx/rx) - Driver sample docs (Chinese): https://hpm-sdk.readthedocs.io/zh-cn/v1.10.0/samples/drivers/i2c/index_zh.html Official site: https://www.hpmicro.com (HPMicro, RISC-V MCU vendor). Download SDK from GitHub releases or https://resource.hpmicro.com/sdk_env_v1.11.0.zip. Build docs locally with Doxygen for full API.
Citations:
- 1: https://hpm-sdk.readthedocs.io/en/latest/api_doc/hpm__i2c__drv_8h_source.html
- 2: https://hpm-sdk.readthedocs.io/en/latest/api_doc/dir_445f1c1c73deccb146d16af27ad5c1d1.html
- 3: https://hpm-sdk.readthedocs.io/zh-cn/v1.10.0/samples/drivers/i2c/index_zh.html
- 4: https://www.developer.sony.com
- 5: https://togithub.com/hpmicro/hpm_sdk
- 6: https://git.durrantlab.pitt.edu/hpmicro/hpm_sdk
- 7: https://github.com/hpmicro/hpm_sdk
- 8: https://hpm-sdk.readthedocs.io/en/v1.10.0/components/i2c/README_en.html
HPM I2C 驱动 API 无法接受 const 缓冲区,使用 const_cast 产生未定义行为。
根据 HPM SDK 官方文档,i2c_master_write() 和 i2c_master_address_write() 的缓冲区参数为非 const uint8_t*,驱动可能在 NACK 或状态处理过程中修改缓冲区内容。当前代码将只读的 data.payload 通过 const_cast 转为可写指针传入,违反 API 契约,构成未定义行为。
建议:将数据复制至可写的临时缓冲区后再调用驱动接口,或在 data.payload 允许的情况下改为非 const 存储。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@firmware/rmcs_board/app/src/i2c/i2c.hpp` around lines 41 - 51, The code
currently const_casts data.payload into a mutable pointer and passes it to HPM
APIs (i2c_master_write and i2c_master_address_write), which can mutate the
buffer; instead allocate a local mutable buffer, copy data.payload into it, and
pass that mutable buffer to i2c_master_write/i2c_master_address_write (use
payload/local_buffer and ensure size uses data.payload.size()); update both
branches (the reg_address branch using i2c_master_address_write and the plain
write branch using i2c_master_write) to use the temporary writable buffer and
free or let it go out of scope after the call.
| void i2c_read_result_deserialized_callback( | ||
| core::protocol::FieldId id, const data::I2cDataView& data) override { | ||
| (void)id; | ||
| (void)data; | ||
| core::utility::assert_failed_always(); | ||
| } | ||
|
|
||
| void i2c_error_deserialized_callback( | ||
| core::protocol::FieldId id, const data::I2cDataView& data) override { | ||
| (void)id; | ||
| (void)data; | ||
| core::utility::assert_failed_always(); | ||
| } |
There was a problem hiding this comment.
不要对“方向错误但可解析”的 I2C 包直接触发硬断言。
当前对 i2c_read_result/i2c_error 的无条件 trap 会把协议输入异常变成设备不可用,建议改为丢弃并上报软错误(或至少 debug 断言)。
🔧 建议修改
void i2c_read_result_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
- core::utility::assert_failed_always();
+ // Unexpected in downlink direction on rmcs_board; drop to keep device alive.
+ return;
}
void i2c_error_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
- core::utility::assert_failed_always();
+ // Unexpected in downlink direction on rmcs_board; drop to keep device alive.
+ return;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@firmware/rmcs_board/app/src/usb/vendor.hpp` around lines 158 - 170, Replace
the unconditional hard assert in i2c_read_result_deserialized_callback and
i2c_error_deserialized_callback with non-fatal handling: detect that the packet
direction is incorrect, drop the message, emit a soft error/reporting call
(e.g., increment an error counter, call a logger or
core::diagnostics::report_error) and optionally use a debug-only assert or log
for development builds; ensure both functions stop calling
core::utility::assert_failed_always() and instead record the error and return
gracefully so the device remains usable.
This PR improves end-to-end I2C behavior across host,
c_board, andrmcs_board.The main goals are:
c_boardc_boardI2C RAM cost for burst workloads such as OLED updateshpm5300evkboard definitions required byrmcs_boardbootloader buildsChanges
Host
kBadAllocas a real failure instead of logging and returning success.c_board
main-loop update.
request + 511-byte payload buffertorequest headers + shared payload chunk pool + DMA staging buffer.(1, 0)to reduce USB contention.I2C0maps to physical STM32I2C2.rmcs_board
hpm_sdksubmodule from132349c9toa82a5354to restorehpm5300evkboard definitions,including flash/XPI NOR macros and
board_init_gpio_pins().Why
Previously, large I2C writes could fail on the host side when the protocol batch buffer was exhausted, but
the API still reported success. That made OLED-style transfers appear successful while silently dropping
part of the request.
On
c_board, queued I2C DMA requests also had unnecessary idle gaps because the next request was notstarted until the main loop reached
i2c0->update()again. In addition, the old queue layout scaled poorlyin RAM because each queued request reserved a full payload buffer.
The
hpm_sdksubmodule update is included because the older submodule state removedhpm5300evkboarddefinitions required by the current
rmcs_boardbootloader code.